package cn.edu.sjtu.process_management.service;

import cn.edu.sjtu.process_management.constant.RespStatus;
import cn.edu.sjtu.process_management.dto.TaskInfo;
import cn.edu.sjtu.process_management.exception.model.OperatorException;
import cn.edu.sjtu.process_management.model.History;
import cn.edu.sjtu.process_management.model.OperatorSet;
import cn.edu.sjtu.process_management.model.Process;
import cn.edu.sjtu.process_management.repository.HistoryRepository;
import cn.edu.sjtu.process_management.repository.OperatorSetRepository;
import cn.edu.sjtu.process_management.repository.ProcessRepository;
import cn.edu.sjtu.process_management.util.ResponseUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;

import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class OperatorSetService {

    @Value("${microServiceManagement.ECNInquiry.url}")
    private String url;

    private final OperatorSetRepository operatorSetRepository;

    private final RestTemplate restTemplate;

    private final HistoryRepository historyRepository;

    private final ProcessRepository processRepository;

    public OperatorSetService(OperatorSetRepository operatorSetRepository,
                              RestTemplate restTemplate,
                              HistoryRepository historyRepository,
                              ProcessRepository processRepository) {
        this.operatorSetRepository = operatorSetRepository;
        this.restTemplate = restTemplate;
        this.historyRepository = historyRepository;
        this.processRepository = processRepository;
    }

    public List<JSONObject> findsECNsOfOperator(String userId) {
        Optional<OperatorSet> os = operatorSetRepository.findOperatorSetByUserId(userId);
        return os.map(operatorSet -> operatorSet.getInstanceIdList().stream()
                .map(i -> {
                    try {
                        JSONObject o = restTemplate
                                .getForObject(String.format("%s?instanceId=%s", url, i), JSONObject.class);
                        return ResponseUtil.simplifyResp(o, i);
                    } catch (HttpClientErrorException e) {
                        return null;
                    }
                })
                .filter(Objects::nonNull)
                .collect(Collectors.toList()))
                .orElseGet(ArrayList::new);
    }

    public JSONObject findTasksOfECN(String instanceId) {
        String reqUrl = instanceId.concat("/api/tasks");
        try {
            JSONObject response = restTemplate.getForObject(reqUrl, JSONObject.class);
            if (response != null) {
                // {..., data: {tasks: {}, type: "auto" | "manual"}}
                // get data: {tasks: {}, type: "auto" | "manual"}
                JSONObject data = response.getJSONObject("data");
                // get tasks: {}
                JSONObject tasks = data.getJSONObject("tasks");
                Map<String, List<TaskInfo>> taskMap = JSON.parseObject(tasks.toJSONString(),
                        new TypeReference<Map<String, List<TaskInfo>>>() {
                        });
                taskMap.forEach((k, v) -> {
                    v.forEach(task -> {
                        String historyId = task.getProcessId();
                        Optional<History> history = historyRepository.findById(historyId);
                        history.ifPresent(h -> {
                            task.setHistoryTime(h.getStart());
                            String processId = h.getProcess();
                            Optional<Process> process = processRepository.findById(processId);
                            process.ifPresent(p -> {
                                task.setProcessName(p.getName());
                            });
                        });
                    });
                });
                // overlap tasks
                data.fluentPut("tasks", taskMap);
                // overlap data
                return response.fluentPut("data", data);
            } else {
                throw new OperatorException(RespStatus.OPERATOR_SET_ERROR);
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new OperatorException(RespStatus.OPERATOR_SET_ERROR);
        }
    }

    public JSONObject operateTask(String operation, String instanceId, JSONObject taskInfo) {
        // e.g. url http://sp-01.root3:8080/api/start
        if (instanceId == null ||
                (!"start".equals(operation) && !"end".equals(operation))) {
            throw new OperatorException(RespStatus.OPERATOR_SET_ERROR);
        }
        String startUrl = String.format("%s/api/%s", instanceId, operation);
        JSONObject jsonObject;
        try {
            jsonObject = restTemplate.postForObject(startUrl, taskInfo, JSONObject.class);
        } catch (Exception e) {
            throw new OperatorException(RespStatus.OPERATOR_SET_ERROR);
        }
        return jsonObject;
    }

    public void updateOperatorSet(String maintainerId, OperatorSet operatorSet) {
        Optional<OperatorSet> os = operatorSetRepository.findOperatorSetByUserId(operatorSet.getUserId());
        if (os.isPresent()) {
            OperatorSet tmp = os.get();
            tmp.getInstanceIdList().removeIf(item -> item.contains(String.format(".%s:", maintainerId)));
            tmp.getInstanceIdList().addAll(operatorSet.getInstanceIdList());
            operatorSetRepository.save(tmp);
            return;
        }
        operatorSetRepository.save(operatorSet);
    }

    public void deleteOperatorSet(String instanceId) {
        List<OperatorSet> operatorSetList = operatorSetRepository.findOperatorSetByInstanceIdListContains(instanceId);
        operatorSetList.forEach(item -> item.getInstanceIdList().removeIf(id -> id.equals(instanceId)));
        operatorSetRepository.saveAll(operatorSetList);
    }

}
